#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Author          : ricsy
@File            : common.py
@Create Time     : 2025/6/9 12:50
@Last Modified   :
@Description     :
"""
import re
import uuid
from typing import Dict

from docutils.parsers.rst import Directive
from ipywidgets import widgets

from src.settings import logger


def deep_merge(base_dict: Dict, override_dict: Dict) -> Dict:
    """深度合并两个字典

    Args:
        base_dict: 基础配置字典
        override_dict: 要覆盖的配置字典

    Returns:
        合并后的新字典
    """
    result = base_dict.copy()
    for key, value in override_dict.items():
        if (
            key in base_dict
            and isinstance(base_dict[key], dict)
            and isinstance(value, dict)
        ):
            result[key] = deep_merge(base_dict[key], value)
        else:
            result[key] = value
    return result


def get_substitutions(directive: Directive) -> dict:
    """获取合并后的变量字典（环境变量 + 页面变量）"""
    try:
        # noinspection PyUnresolvedReferences
        env = directive.state.document.settings.env

        # 1. 获取全局变量（conf.py 中定义）
        env_config = getattr(env, "config", {})
        global_subs = getattr(env_config, "myst_substitutions", {})

        # 2. 获取页面变量（通过 substitutions 指令定义）
        # TODO toctree 指令执行时，substitutions 指令可能还未处理即加载顺序问题会导致一般为空，预留代码
        doc_subs = getattr(env, "temp_data", {}).get("substitutions", {})

        # 3. 合并变量（页面变量优先）
        return {**global_subs, **doc_subs}

    except Exception as e:
        logger.warning(f"获取变量失败: {str(e)}")
        return {}


def process_caption(directive: Directive, caption_text):
    """处理变量替换"""
    try:
        # 从环境获取替换变量
        substitutions = get_substitutions(directive)
        if not substitutions:
            return caption_text

        # 编译正则表达式（预编译提升性能）
        var_pattern = re.compile(r"\{\{\s*(\w+)\s*}}")

        # 替换处理
        def replacer(match: re.Match) -> str:
            var_name = match.group(1)
            return substitutions.get(var_name, match.group(0))

        return var_pattern.sub(replacer, caption_text)
    except Exception as e:
        logger.warning(f"变量替换失败: {e}")
        return caption_text


# noinspection PyShadowingBuiltins
def validate_numeric(arg, min_val=None, max_val=None):
    """通用数值验证函数"""
    try:
        value = float(arg)
        if min_val is not None and value < min_val:
            raise ValueError(f"Value {value} is below minimum allowed value {min_val}")
        if max_val is not None and value > max_val:
            raise ValueError(f"Value {value} exceeds maximum allowed value {max_val}")
        return value
    except (ValueError, TypeError) as e:
        raise ValueError(
            f"Invalid numeric value: '{arg}'. Must be a number"
            f"{f' between {min_val} and {max_val}' if min_val is not None and max_val is not None else ''}"
            f"{f' >= {min_val}' if min_val is not None and max_val is None else ''}"
            f"{f' <= {max_val}' if max_val is not None and min_val is None else ''}"
        ) from e


def validate_bool(arg):
    """布尔值验证函数（支持无值标记，如：:blink:）"""
    if arg is None:
        return True
    if isinstance(arg, str):
        return (
            arg.lower() == "true"
            or arg.lower() == "on"
            or arg.lower() == "yes"
            or arg.lower() == "1"
        )
    return bool(arg)


def create_controls(options: dict):
    """根据配置自动生成交互控件"""
    controls = {}
    for param, config in options.items():
        # 基础参数
        kwargs = {"description": config["desc"], "value": config["value"]}

        widget_class = config["widget"]
        # 处理有选项的控件
        if hasattr(config["widget"], "options"):
            kwargs["options"] = config.get("options", [])
        # 特殊控件处理
        if widget_class == widgets.ToggleButtons:
            kwargs["button_style"] = ""  # 默认按钮样式
        # 创建控件实例
        controls[param] = config["widget"](**kwargs)
    return controls


def init_buttons(on_generate_click, on_reset_click):
    """初始化按钮及事件"""
    generate_btn = widgets.Button(description="生成指令", button_style="success")
    reset_btn = widgets.Button(description="恢复默认", button_style="warning")

    on_generate_click(None)
    generate_btn.on_click(on_generate_click)
    reset_btn.on_click(on_reset_click)
    generate_btn.add_class("create-config-btn")
    reset_btn.add_class("reset-btn")
    return generate_btn, reset_btn


class ConfigurableWidget:
    """带固定标识的可配置控件"""

    def __init__(self, param_name, widget_type, default_value, class_prefix, **kwargs):
        self.param_name = param_name
        self.default_value = default_value
        self.default_attr = f"{class_prefix}-{self.param_name}"
        self.widget = widget_type(**kwargs)
        self._add_identifiers()

    def _add_identifiers(self):
        """添加固定标识"""
        self.widget.layout.object_id = (
            f"widget-{self.param_name}-{uuid.uuid4().hex[:8]}"
        )
        self.widget.add_class(self.default_attr)

    @property
    def value(self):
        """获取当前值"""
        return self.widget.value

    def observe(self, callback):
        """注册观察者"""
        self.widget.observe(callback, names="value")

    def reset(self):
        """重置到默认值"""
        self.widget.value = self.default_value


def init_controls(options, class_prefix):
    """根据配置动态初始化控件"""
    _controls = {}
    for param_name, config in options.items():
        # 基础参数
        kwargs = {
            "description": config["desc"],
            "value": config["value"],
            "tooltip": config.get("tooltip", ""),
        }

        # 特殊参数处理
        if config["widget"] == widgets.Dropdown:
            kwargs["options"] = config.get("options", [])
            if not kwargs["options"]:
                raise ValueError(
                    f"Dropdown组件参数为空：options，请检查'{param_name}'配置！"
                )
        elif config["widget"] == widgets.ToggleButtons:
            kwargs["options"] = config.get("options", [])
            if not kwargs["options"]:
                raise ValueError(
                    f"ToggleButtons组件参数为空：options，请检查'{param_name}'配置！"
                )
            kwargs["button_style"] = ""  # 默认按钮样式
        if config["widget"] in (widgets.IntSlider, widgets.FloatSlider):
            kwargs.update(config.get("params", {}))
            if not config.get("params", {}):
                raise ValueError(
                    f"IntSlider组件参数为空：params，请检查'{param_name}'配置！"
                )

        # 创建控件实例
        _controls[param_name] = ConfigurableWidget(
            param_name=param_name,
            widget_type=config["widget"],
            default_value=config["value"],
            class_prefix=class_prefix,
            **kwargs,
        )
    return _controls
